package exams.concurrent;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DeadLockExample {

    static Lock lock = new ReentrantLock();

    static Allocator allocator = new Allocator();

    public static void main(String[] args) throws InterruptedException {
        Account accountA = new Account(1000);
        Account accountB = new Account(1000);

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                accountA.transfer(accountB, 100);
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                accountB.transfer(accountA, 200);
            }
        });
        threadA.start();
        threadB.start();

        threadA.join();
        threadB.join();
        System.out.println(accountA.balance);
        System.out.println(accountB.balance);
    }

    static class Account {

        public double balance;

        public Account(double balance) {
            this.balance = balance;
        }

        public void transfer(Account target, double amt) {
            allocator.apply(this, target);
            synchronized (this) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (target) {
                    this.balance -= amt;
                    target.balance += amt;
                }
            }
            allocator.free();
        }

    }

    static class Allocator {

        private final Account[] accounts;

        public Allocator() {
            this.accounts = new Account[2];
        }

        synchronized void apply(Account account1, Account account2) {
            while (accounts[0] != null || accounts[1] != null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            accounts[0] = account1;
            accounts[1] = account2;
        }

        synchronized void free() {
            accounts[0] = null;
            accounts[1] = null;
            notifyAll();
        }

    }

}
